package simulador;

/**Biblioteca mips*/
import java.util.logging.Level;
import java.util.logging.Logger;
//import mips.*;
//import mips.estructurasMemoria.*;

/**Manipulación de archivos*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;

/**Vector*/
import java.util.Vector;

/**Semáforos*/
import java.util.concurrent.Semaphore;
//import mips.Contexto;


/**
 * @author Fabián Guevara
 * @date 17-6-9
 * Esta clase representa propiamente la computadora con arquitectura de
 * procesador mips. Un objeto Computadora es el encargado de
 * instanciar objetos memoria, núcleo y demás componentes de un ordenador.
 * La clase Computadora hace además las veces de sistema operativo, con labores
 * como cambios de contexto y sincronización para los ticks de reloj.
 *  Se puede pensar en esta clase como el flujo o programa principal.
 *  Dado que esta clase -y no la interfaz gráfica- es la que tiene contacto
 * con los registros, cachés y demás estructuras de la arquitectura mips,
 * la clase Computadora es responsable de actualizar los datos que se
 * muestran en la interfaz gráfica. Para ello, la interfaz gráfica debe
 * proveer todos los métodos públicos necesarios para cambiar el contenido
 * de sus componentes de GUI.
 */
public class Computadora implements Runnable{
    /**Referencia a la interfaz gráfica, mediante la cual se actualizan
     * los valores que se muestran al usuario en los componentes de GUI.
     */
    private final SimuladorView interfaz;
    /**Arreglo de instrucciones de los hilos, dispuestas consecutivamente
     * en el orden del programa.
     */
    private final Palabra[] hilos;
    /**Quantum del protocolo round-robin para asignación del procesador*/
    private final int quantum;
    /**Número de ciclos que tardan los accesos a memoria*/
    private final int tiempoAccesoMemoria;
    /**Contexto de cada hilo*/
    private final Contexto[] contextos;
    /**Arreglo con la dirección (en byte, no en Palabra) inicial
     * de cada hilo
     */
    private final int[] pcInicial;
    /**Indica el ciclo actual*/
    private int reloj;
    
    private final Nucleo nucleo1;

    private final Nucleo nucleo2;
    /**Memoria principal del sistema*/
    private final Memoria memoria;
    /**Semáforo para sincronizar la computadora con ambos núcleos*/
    private final Semaphore mutexTicks;
    /**Objeto para lograr la exclusión mutua en los accesos a caché de
     cada núcleo*/
    Object mutexCache;
    
    /**Constructor. Prepara todo el ambiente para la simulación, de modo que
     * ésta pueda comenzar en cualquier momento una vez creado el objeto
     * Computadora.
     * @param s referencia a la interfaz gráfica donde el objeto Computadora
     * debe actualizar los datos mostrados al usuario
     * @param a archivos de los hilos
     * @param q quantum del protocolo round-robin para asignación del
     * procesador
     * @param t tiempo que tardan los accesos a memoria
     */
    public Computadora(SimuladorView s, File[] a, int q, int t){
        this.contextos = new Contexto[a.length];
        this.pcInicial = new int[a.length];
        this.hilos = this.parseToPalabraArray(a);
        this.interfaz = s;
        this.quantum = q;
        this.tiempoAccesoMemoria = t;
        this.reloj = 0;
        this.incializarContexto();
        this.memoria = new Memoria();
        this.memoria.cargarHilos(this.hilos); //carga los hilos en memoria
        this.mutexTicks = new Semaphore(2); //Pueden pasar 2 a la vez (los 2 núcleos)
        //y Computadora solo puede pasar si ningún núcleo tiene el pase. Esto se
        //logra haciendo que Computadora haga aquire(2) (es decir, ocupa 2 
        //permisos) y cada núcleo hace aquire(1) (es decir, con que haya un
        //permiso basta). Análogamente, computadora hace release(2) y cada
        //núcleo hace release(1)
        mutexCache = new Object();
        nucleo1 = new Nucleo(memoria,
                                    this.pcInicial[0], //primer hilo
                                    mutexTicks,
                                    mutexCache,
                                    this.quantum,
                                    this.tiempoAccesoMemoria);
        nucleo2 = new Nucleo(memoria,
                                    this.pcInicial[1],//segundo hilo
                                    mutexTicks,
                                    mutexCache,
                                    this.quantum,
                                    this.tiempoAccesoMemoria);
        nucleo1.asociarCacheDatosDelOtroNucleo(nucleo2);
        nucleo2.asociarCacheDatosDelOtroNucleo(nucleo1);
    }

    private void incializarContexto(){
        for(int i = 0; i< this.contextos.length ;i++){
            this.contextos[i] = new Contexto(i);
            this.contextos[i].setHalt(false);
            this.contextos[i].setPC(pcInicial[i]);
        }
    }

    /**Inicia la simulación */
    public void run(){
       nucleo1.setReloj(reloj);
       nucleo2.setReloj(reloj);

       Contexto[] contextosNucleo1 = new Contexto[this.contextos.length/2];
       Contexto[] contextosNucleo2 = new Contexto[this.contextos.length - contextosNucleo1.length];
       int indice1 = 0;
       int indice2 = 0;

       for(int i = 0; i < this.contextos.length; i++){
           if(i < this.contextos.length/2)
            contextosNucleo1[indice1++] = this.contextos[i];
           else
               contextosNucleo2[indice2++] = this.contextos[i];
       }

       nucleo1.setContextos(contextosNucleo1, 1);
       nucleo2.setContextos(contextosNucleo2, 2);


       //nucleo1.run(); FAB: Esto no puede ser así. Para que los núcleos arranquen
       //nucleo2.run(); como hilos, deben hacer new Thread(nucleo1, "algún nombre").start();
       //cualquier cosa, miren cómo arranco yo al hilo Computadora en
       //SimuladorView.jButton1ActionPerformed(java.awt.event.ActionEvent evt)
       boolean continuar = true;
       int quantumTemp = this.quantum;
       int relojTemp;
       int terminaron = 0;

       

       while(continuar){ //Hay que revisar cuándo es que para el programa.
            //mutexTicks.acquire(2);
           //mutexTicks.acquire(2) throws new InterruptedException("Hola");
            try {
                //Hay que revisar cuándo es que para el programa.
                //mutexTicks.acquire(2);
                //mutexTicks.acquire(2) throws new InterruptedException("Hola");
                mutexTicks.acquire(2);
                //nucleo1.ejecutar(this.contextos[indiceContexto1],quantumTemp);
                //nucleo2.ejecutar(this.contextos[indiceContexto2],quantumTemp);
                //this.interfaz.actualizarCacheNucleo1(nucleo1.getContenidoCacheDatos());
                mutexTicks.release(2);
                this.reloj += this.quantum;
                if (nucleo1.getReloj() > nucleo2.getReloj()) {
                    relojTemp = nucleo1.getReloj();
                } else {
                    relojTemp = nucleo2.getReloj();
                }
                if (relojTemp > this.reloj) {
                    if (relojTemp - this.reloj <= 0) {
                        quantumTemp = this.quantum;
                    } else {
                        quantumTemp = this.quantum - (relojTemp - this.reloj);
                    }
                    this.reloj = relojTemp;
                }
                nucleo1.setReloj(reloj);
                nucleo2.setReloj(reloj);
                for (int i = 0; i < this.contextos.length; i++) {
                    if (this.contextos[i].getHalt() == true) {
                        terminaron++;
                    }
                }
                if (terminaron == this.contextos.length) {
                    continuar = false;
                }
                
                javax.swing.JOptionPane.showMessageDialog(null, "la que te parió");
            } catch (InterruptedException ex) {
                Logger.getLogger(Computadora.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private Palabra[] parseToPalabraArray(File[] archivo){
        Vector resultado = new Vector();//resultado temporal
        BufferedReader br;//lector del archivo
        String linea = "";//línea del archivo leída
        String[] tokens;//cada componente de la instrucción
        //iterar sobre los archivos
        for(int i = 0; i < archivo.length; i++){
            this.pcInicial[i] = resultado.size()*4;//fabian: si quisiéramos
            //guardar la dirección en número de palabra y no de byte,
            //basta con quitar este "*4"
            try {
                br = new BufferedReader(new FileReader(archivo[i]));
                //Cuando llega al fin de archivo, readLine() devuelve null
                while((linea = br.readLine()) != null){
                    //tokenizar la entrada y guardar los tokens en los bytes de las palabras
                    tokens = linea.split("\\s");
                    resultado.add(new Palabra(Integer.parseInt(tokens[0]),
                                              Integer.parseInt(tokens[1]),
                                              Integer.parseInt(tokens[2]),
                                              Integer.parseInt(tokens[3])
                                              )
                                  );
                    /*System.out.println(tokens[0]);
                    System.out.println(tokens[1]);
                    System.out.println(tokens[2]);
                    System.out.println(tokens[3]);*/


                }
                br.close();
            } catch (FileNotFoundException e) {
              e.printStackTrace();
            } catch (IOException e) {
              e.printStackTrace();
            }
        }
        //Convertirlo a arreglo de palabras
        Palabra[] resultadoFinal = new Palabra[resultado.size()];
        resultado.copyInto(resultadoFinal);

        /*System.out.println("hilo:");
        for(int i=0; i < resultado.size(); i++){
            System.out.println(resultadoFinal[i].toString());
        }*/
        return resultadoFinal;
    }
}
